
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.*;
import java.util.*;

// a class representing an AVL GUI
public class AVLGUI {
	ArrayList<JTextArea> sourcelines;
	Set<JTextArea> highlightedlines;
	JFrame window;
	JPanel sourcepanel;
	JPanel watchpanel;
	JPanel watchpanelbasics;
	Set<AVLBasicType> basics;
	Set<AVLArray> arrays;
	Set<AVLMatrix> matrices;
	boolean paused;
	boolean blocked;
	int steptime;
	final Thread listener;
	final JButton playpause;
	final JTextField steptimefield;
	final int DEFAULTSTEPTIME = 1000;

	// constructs an AVL GUI
	public AVLGUI(String source) {

		// create data structures
		basics = new HashSet<AVLBasicType>();
		arrays = new HashSet<AVLArray>();
		matrices = new HashSet<AVLMatrix>();
		highlightedlines = new HashSet<JTextArea>();
		sourcelines = new ArrayList<JTextArea>();

		// create window
		window = new JFrame(source + " - " + "AVL");
		window.setBounds(10, 10, 800, 600);

		// set up window layout
		window.setLayout(new BorderLayout());

		// create thread to unblock at step time intervals
		paused = false;
		blocked = false;
		steptime = DEFAULTSTEPTIME;
		listener = new Thread() {
			public void run() {
				while(true) {
					try {
						Thread.sleep(steptime);
						if(!paused)
							blocked = false;
					} catch (InterruptedException e) {
						// sleep with new step time
					}
				}
			}
		};
		listener.setDaemon(true);
		listener.start();
		
		// set up execution flow button panel
		JPanel buttonpanel = new JPanel(new GridLayout(1, 0));
		steptimefield = new JTextField(Integer.toString(steptime));
		steptimefield.setHorizontalAlignment(JTextField.CENTER);
		steptimefield.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent arg0) {
				try {
					steptime = Integer.parseInt(steptimefield.getText());
					listener.interrupt();
				} catch(NumberFormatException e) {
					steptimefield.setText(Integer.toString(steptime));
				}
			}
		});
		
		final JButton steptimebutton = new JButton("set");
		steptimebutton.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent arg0) {
				try {
					steptime = Integer.parseInt(steptimefield.getText());
					listener.interrupt();
				} catch(NumberFormatException e) {
					steptimefield.setText(Integer.toString(steptime));
				}
			}
		});
		buttonpanel.add(steptimebutton);
		buttonpanel.add(steptimefield);
		playpause = new JButton("pause");
		playpause.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent arg0) {
				if(playpause.getText().equals("play")){
					playpause.setText("pause");
					paused = false;
				}
				else {
					playpause.setText("play");
					paused = true;
				}
			}
		});
		buttonpanel.add(playpause);
		final JButton step = new JButton("step");
		step.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent arg0) {
				blocked = false;
			}
		});
		buttonpanel.add(step);
		window.add(buttonpanel, BorderLayout.SOUTH);

		// create source panel
		sourcepanel = new JPanel();

		// set up source panel layout
		GridBagLayout sourcelayout = new GridBagLayout();
		sourcepanel.setLayout(sourcelayout);
		sourcepanel.setBackground(Color.WHITE);
		GridBagConstraints sourceconstraints = new GridBagConstraints();
		sourceconstraints.fill = GridBagConstraints.HORIZONTAL;
		sourceconstraints.weightx = 1;
		sourceconstraints.weighty = 0;

		// open the source file
		BufferedReader in = null;
		try {
			in = new BufferedReader(new FileReader(source));
		} catch (FileNotFoundException e) {
			System.out.println("Error: file not found.");
		}

		// read the source file into the GUI
		try {
			int i = 0;
			while(in.ready()) {
				JTextArea ta = new JTextArea(in.readLine());
				ta.setTabSize(3);
				ta.setBackground(Color.WHITE);
				ta.setEditable(false);
				sourceconstraints.gridy = i++;
				sourcelines.add(ta);
				sourcepanel.add(ta);
				sourcelayout.setConstraints(ta, sourceconstraints);
			}
		} catch (IOException e) {
			System.out.println("Error: I/O exception.");
		}

		// set up source panel scrolling
		JScrollPane sourcescroll = new JScrollPane(sourcepanel);
		sourcescroll.setBorder(null);
		sourcescroll.setBackground(Color.WHITE);

		// create watch panel
		watchpanel = new JPanel();
		watchpanel.setBackground(Color.WHITE);

		// set up watch panel layout
		GridBagLayout watchlayout = new GridBagLayout();
		watchpanel.setLayout(watchlayout);
		watchpanel.setBackground(Color.WHITE);

		// generate panel of watched basic types
		watchpanelbasics = new JPanel();
		FlowLayout fl = new FlowLayout();
		fl.setAlignment(FlowLayout.RIGHT);
		watchpanelbasics.setLayout(fl);
		watchpanelbasics.setBackground(Color.WHITE);

		// set up watch panel vertical scrolling
		JScrollPane watchscroll = new JScrollPane(watchpanel);
		watchpanel.setPreferredSize(new Dimension(watchscroll.getViewport().getSize().width, watchpanel.getPreferredSize().height));
		watchscroll.setBorder(null);
		watchscroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
		watchscroll.setBackground(Color.WHITE);

		// set up window
		JSplitPane splitpane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, sourcescroll, watchscroll);
		splitpane.setDividerLocation(300);
		splitpane.setBackground(Color.WHITE);
		splitpane.setContinuousLayout(true);
		window.add(splitpane);

		// display window
		window.setVisible(true);
		window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}

	// highlights the given line light gray
	// line indexing starts at one
	// assumes the line number exists
	synchronized void highlightSourceLine(int line) {
		sourcelines.get(line-1).setBackground(Color.LIGHT_GRAY);
		sourcelines.get(line-1).update(window.getGraphics());
		highlightedlines.add(sourcelines.get(line-1));
	}

	// highlights the given line light green if true or light red if false
	// line indexing starts at one
	// assumes the line number exists
	synchronized void highlightSourceLine(int line, boolean bool) {
		if(bool)
			sourcelines.get(line-1).setBackground(new Color(50, 255, 50));
		else
			sourcelines.get(line-1).setBackground(new Color(255, 50, 50));
		sourcelines.get(line-1).update(window.getGraphics());
		highlightedlines.add(sourcelines.get(line-1));
	}

	// removes all current source line highlights
	synchronized void clearSourceLineHighlights() {
		for(JTextArea ta : highlightedlines) {
			ta.setBackground(Color.WHITE);
			ta.update(window.getGraphics());
		}
		highlightedlines.clear();
	}

	// generates the watch panel from the watched variables
	synchronized private void generateWatchPanel() {

		// clear watch panel and retrieve watch panel layout
		watchpanel.removeAll();
		GridBagLayout watchlayout = (GridBagLayout)watchpanel.getLayout();

		// set up watch panel layout constraints
		GridBagConstraints watchconstraints = new GridBagConstraints();
		watchconstraints.fill = GridBagConstraints.HORIZONTAL;
		watchconstraints.weightx = 1;
		watchconstraints.weighty = 0;
		watchconstraints.insets = new Insets(5, 5, 0, 5);
		int scrollBarSize = ((Integer)UIManager.get("ScrollBar.width")).intValue();
		int i = 0;
		int minimumverticalsize = 0;

		// generate panel of watched basic types
		watchpanelbasics.removeAll();
		for(AVLBasicType bt : basics) {
			watchpanelbasics.add(bt);
		}

		// add panel of watched basic types
		watchconstraints.gridy = i++;
		JScrollPane jsp = new JScrollPane(watchpanelbasics);
		jsp.setBorder(null);
		jsp.setBackground(Color.WHITE);
		jsp.setMinimumSize(new Dimension(watchpanelbasics.getMinimumSize().width, watchpanelbasics.getMinimumSize().height + scrollBarSize));
		jsp.setPreferredSize(new Dimension(watchpanelbasics.getPreferredSize().width, watchpanelbasics.getPreferredSize().height + scrollBarSize));
		watchpanel.add(jsp);
		watchlayout.setConstraints(jsp, watchconstraints);
		minimumverticalsize += jsp.getMinimumSize().height;

		// add panels for each watched array
		for(AVLArray a : arrays) {

			// create container to right-justify array
			FlowLayout fl = new FlowLayout(FlowLayout.RIGHT);
			fl.setVgap(0);
			fl.setHgap(0);
			JPanel rightbuffer = new JPanel(fl);
			rightbuffer.add(a);
			rightbuffer.setBackground(Color.WHITE);

			// create container to provide scrolling
			jsp = new JScrollPane(rightbuffer);
			jsp.setBorder(null);
			jsp.setBackground(Color.WHITE);
			jsp.setPreferredSize(new Dimension(a.getPreferredSize().width, a.getPreferredSize().height + scrollBarSize));
			jsp.setMinimumSize(new Dimension(0, a.getMinimumSize().height + scrollBarSize));
			watchpanel.add(jsp);
			watchconstraints.gridy = i++;
			watchlayout.setConstraints(jsp, watchconstraints);
			minimumverticalsize += jsp.getMinimumSize().height;
		}

		// set up panel for watched matrices
		JPanel matrixpanel = new JPanel();
		GridLayout gl = new GridLayout(0,1);
		gl.setVgap(5);
		matrixpanel.setLayout(gl);
		matrixpanel.setBackground(Color.WHITE);
		matrixpanel.setMinimumSize(new Dimension(0, 0));
		int matrixpanelminimumsize = 0;

		// populate panel of watched matrices
		for(AVLMatrix m : matrices) {

			// create container to right-justify matrix
			FlowLayout fl = new FlowLayout(FlowLayout.RIGHT);
			fl.setVgap(0);
			fl.setHgap(0);
			JPanel rightbuffer = new JPanel(fl);
			rightbuffer.add(m);
			rightbuffer.setBackground(Color.WHITE);

			// create container to provide scrolling
			jsp = new JScrollPane(rightbuffer);
			jsp.setBorder(null);
			jsp.setBackground(Color.WHITE);
			jsp.setPreferredSize(new Dimension(m.getMinimumSize().width + scrollBarSize, m.getMinimumSize().height + scrollBarSize));
			jsp.setMinimumSize(new Dimension(0, m.getMinimumSize().height + scrollBarSize));
			matrixpanelminimumsize += m.getMinimumSize().height;
			matrixpanel.setMinimumSize(new Dimension(0, matrixpanelminimumsize));
			matrixpanel.add(jsp);
			minimumverticalsize += jsp.getMinimumSize().height;
		}

		// add panel of watched matrices
		watchpanel.add(matrixpanel);
		watchconstraints.gridy = i++;
		watchconstraints.weighty = 1;
		watchconstraints.fill = GridBagConstraints.BOTH;
		watchlayout.setConstraints(matrixpanel, watchconstraints);

		// update watchpanel
		watchpanel.revalidate();
		watchpanel.setPreferredSize(new Dimension(watchpanel.getPreferredSize().width, minimumverticalsize));
	}

	// adds an object to the list of variables being watched
	public void watch(final Object o) {
		
		// send instructions to GUI thread
		Runnable invokedlater = new Runnable () {
			public void run () {
				if(o instanceof AVLBasicType) {
					basics.add((AVLBasicType)o);
				}
				if(o instanceof AVLArray) {
					arrays.add((AVLArray)o);
				}
				if(o instanceof AVLMatrix) {
					matrices.add((AVLMatrix)o);
				}
				generateWatchPanel();
				watchpanel.revalidate();
				watchpanel.repaint();
			}
		};
		SwingUtilities.invokeLater(invokedlater);
	}

	// removes an object to the list of variables being watched
	public void hide(final Object o) {

		// send instructions to GUI thread
		Runnable invokedlater = new Runnable () {
			public void run () {
				if(o instanceof AVLBasicType) {
					basics.remove((AVLBasicType)o);
				}
				if(o instanceof AVLArray) {
					arrays.remove((AVLArray)o);
				}
				if(o instanceof AVLMatrix) {
					matrices.remove((AVLMatrix)o);
				}
				generateWatchPanel();
				watchpanel.repaint();
			}
		};
		SwingUtilities.invokeLater(invokedlater);
	}

	// clears all current highlights
	public void resetAll() {

		// set up reset signal
		final Set<Object> resetflag = new HashSet<Object>();
		resetflag.clear();

		// send instructions to GUI thread
		Runnable invokedlater = new Runnable () {
			public void run () {
				for(AVLBasicType b : basics) {
					b.reset();
				}
				for(AVLArray a : arrays) {
					a.reset();
				}
				for(AVLMatrix m : matrices) {
					m.reset();
				}
				watchpanel.revalidate();
				watchpanel.repaint();
				clearSourceLineHighlights();
				resetflag.add(new Object());
			}
		};
		SwingUtilities.invokeLater(invokedlater);

		// wait for signal that reset is complete
		while(resetflag.isEmpty()) {
			try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	// controls the flow of execution
	// calls resetAll before returning
	public void block() {

		// wait for unblock signal
		blocked = true;
		while(blocked) {
			try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

		// remove all current highlights
		resetAll();
	}

	// controls the flow of execution
	// same as block, but also disables automatic stepping
	public void pause() {

		// disable stepping
		paused = true;
		boolean alreadypaused = playpause.getText().equals("play");
		if(!alreadypaused)
			playpause.setText("play");

		// wait for unblock signal
		blocked = true;
		while(blocked) {
			try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

		// remove all current highlights
		resetAll();
	}

	// sets the current step time
	public void setStepTime(int st) {
		steptime = st;
		steptimefield.setText(Integer.toString(steptime));
		listener.interrupt();
	}
	
	// utility function which evaluates a boolean, highlights a source line red or green, and blocks
	// returns the value of the given boolean
	public boolean controlledEvaluation(boolean b, int line) {
		highlightSourceLine(line, b);
		block();
		return b;
	}
}

